﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using ReactiveGraph.Core;
using ReactiveGraph.Core.Extensions;

namespace ReactiveGraph.Object
{
    public class ReactiveObjectCollection<T> : ReactiveObject<T>,IList<T> 
        where T : ReactiveObject<T>
    {
        private readonly IReactiveNode _self;

        public IObservable<T> ChildAdded() 
        {
            return Events
                .Where(e => e.Type == EventType.DescendantAdded && this == e.Source.ParentNode)
                .Select(e => e.Source.Node)
                .Cast<T>();
        }

        public IObservable<T> ChildRemoved()
        {
            return Events
                .Where(e => e.Type == EventType.DescendantRemoved && this == e.Source.ParentNode)
                .Select(e => e.Source.Node)
                .Cast<T>();
        }

        public IObservable<IProperty> ChildPropertyChanged()
        {
            return Events
                .Where(e => e.Type == EventType.PropertyChange && Contains((T) e.Source.Node))
                .Select(e => e.Source.Property);
        }

        public IObservable<IProperty> DescendantPropertyChanged(Func<T,IProperty> property)
        {
            return Events
                .Where(e => e.Type == EventType.PropertyChange && 
                            e.Source.Path.Steps.ElementAt(1).GetType() == typeof(T) &&
                            property((T)e.Source.Path.Steps.ElementAt(1)) == e.Source.Property)
                .Select(e => e.Source.Property);
        }

        public IObservable<IProperty> DescendantPropertyChanged()
        {
            return Events
                .Where(e => e.Type == EventType.PropertyChange)
                .Select(e => e.Source.Property);
        }

        public IObservable<Unit> SyncChildProperties(Func<T,IProperty> selectProperty)
        {
            var syncToNewChild = ChildAdded() 
                .Do(c => SyncAnyToChild(selectProperty, c)).ToUnit();
            var syncExpiryToExistingChilds = DescendantPropertyChanged(selectProperty) 
                .Do(p => SyncToOtherChildren(selectProperty, p))
                .ToUnit();
            return syncToNewChild.Merge(syncExpiryToExistingChilds);
        }

        private void SyncToOtherChildren(Func<T,IProperty> selectProperty,IProperty p)
        {
            this.Select(selectProperty).Run(other => other.Value = p.Value);
        }

        private object SyncAnyToChild(Func<T, IProperty> selectProperty, T child)
        {
            return selectProperty(child).Value =this.Select(v => selectProperty(v).Value).FirstOrDefault();
        }

        public ReactiveObjectCollection(Context context)
            : base()
        {
            _self = this;
        }

        public void Add(T node)
        {
            _self.ChildNodes.Add(node);
        }

        public void AddNew()
        {
            Add(Context.Factories.CreateNodeForType(typeof(T)) as T);
        }

        public void AddNew(string name)
        {
            Add(Context.Factories.CreateNamedNodeForType(name,typeof(T)) as T);
        }

        public void Clear()
        {
            _self.ChildNodes.Clear();  
        }

        public bool Contains(T item)
        {
            return _self.ChildNodes.Contains(item);
        }

        public void CopyTo(T[] array, int arrayIndex)
        {
            _self.ChildNodes.ToList().CopyTo(array,arrayIndex);
        }

        public bool Remove(T item)
        {
            _self.ChildNodes.Remove(item);
            return true;
        }

        public int Count
        {
            get
            {
                return _self.ChildNodes.Count();
            }
        }

        public bool IsReadOnly
        {
            get { return false; }
        }

        public IEnumerator<T> GetEnumerator()
        {
            return _self.ChildNodes.Cast<T>().GetEnumerator();
        }

        IEnumerator IEnumerable.GetEnumerator()
        {
            return GetEnumerator();
        }

        public int IndexOf(T item)
        {
            return _self.ChildNodes.IndexOf(item);
        }

        public void Insert(int index, T item)
        {
            _self.ChildNodes.Insert(index, item);
        }

        public void RemoveAt(int index)
        {
            _self.ChildNodes.RemoveAt(index);
        }

        public T this[int index]
        {
            get { return (T) _self.ChildNodes[index]; }
            set { _self.ChildNodes[index] = value; }
        }


        
    }
}